﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;
using Elderos.Dialogs;
using Elderos.Utils;
using TreeBuilder.Settings;
using Path = System.IO.Path;


namespace TreeBuilder
{
    /// <summary>
    /// Interaction logic for Tree.xaml
    /// </summary>
    public partial class Tree : UserControl, IXmlSerializable
    {
        public Tree()
        {
            InitializeComponent();
            History.ExecutingAction += OnExecutingAction;
        }

        public bool OpeningFromFile { get; set; }

        private bool _isSaved;
        public bool IsSaved
        {
            get { return _isSaved; }
            set
            {
                _isSaved = value;
                RefreshDisplayName();
            }
        }

        private string _filename;
        public string Filename
        {
            get { return _filename; }
            set
            {
                _filename = value;
                RefreshDisplayName();
            }
        }

        public void RefreshDisplayName()
        {
            DisplayName = (Path.GetFileName(Filename) ?? "Tree " + DocumentNo) + (IsSaved ? "" : "*");
        }

        public int DocumentNo { get; set; }

        public string DisplayName
        {
            get { return (string)GetValue(DisplayNameProperty); }
            set { SetValue(DisplayNameProperty, value); }
        }

        public ActionHistory History
        {
            get { return _history; }
        }

        public static readonly DependencyProperty DisplayNameProperty =
            DependencyProperty.Register("DisplayName", typeof(string), typeof(Tree), new UIPropertyMetadata(""));

        private readonly ActionHistory _history = new ActionHistory();

        #region Create node

        private void CreateTreeNode(MouseButtonEventArgs e)
        {
            var node = new TreeNode();
            node.TextBox.Text = "Enter the text";
            node.Visibility = Visibility.Hidden;
            SetNodeEvents(node);

            var destination = GetTreeNodePosition(e.GetPosition(TreeCanvas), node);

            var action = new CreateNodeAction(TreeCanvas, node, destination);
            History.ExecuteAction(action);
        }

        private void SetNodeEvents(TreeNode node)
        {
            node.DragInit += DragInit;
            node.DragOver += TreeCanvas_DragOver;
            node.Delete += DeleteNodes;
            node.Selected += SelectElement;
            node.FontChanged += FontChanged;
            node.MenuOpened += OnObjectMenuOpened;
            node.MenuClosed += OnObjectMenuClosed;
            node.TextChanged += NodeTextChanged;

            SetBindingEvents(node);
        }

        #endregion

        #region Drag&Drop
        private void DragInit(object sender, DragInitEventArgs e)
        {
            var nodes = TreeCanvas.Children.OfType<INode>()
                                  .Where(x => x.IsSelected)
                                  .ToList();
            if (!nodes.Contains(e.InitNode))
                nodes.Add(e.InitNode);

            var initOffset = Point.Subtract(e.MousePosition, e.InitNode.TopCenter);

            foreach (var node in nodes)
            {
                node.IsBeingDragged = true;
                node.Offset = new Point(initOffset.X, initOffset.Y);
            }

            Dictionary<INode, Point> offsets =
                nodes.ToDictionary(x => x,
                                   x => new Point(x.TopCenter.X - e.InitNode.TopCenter.X, x.TopCenter.Y - e.InitNode.TopCenter.Y));

            Dictionary<INode, Point> initPositions = nodes.ToDictionary(x => x, x => x.TopCenter);

            var dragGroup = new DragGroup() {InitNode = e.InitNode, Offsets = offsets, InitPositions = initPositions};

            Debug.WriteLine("----Begin dragging----");
            Debug.WriteLine("Init node: " + dragGroup.InitNode.TopCenter + "\t" + dragGroup.InitNode.Offset + "\t" +
                            dragGroup.Offsets[dragGroup.InitNode]);
            foreach (var kv in dragGroup.Offsets)
            {
                Debug.WriteLine("Node: " + kv.Key.TopCenter + "\t" + kv.Key.Offset + "\t" + offsets[kv.Key]);
            }

            var data = new DataObject(dragGroup);
            DragDrop.DoDragDrop(this, data, DragDropEffects.All);
        }

        private class DragGroup
        {
            public INode InitNode { get; set; }
            public Dictionary<INode, Point> Offsets { get; set; }
            public Dictionary<INode, Point> InitPositions { get; set; } 
        }

        private void TreeCanvas_DragOver(object sender, DragEventArgs e)
        {
            HandleDragOver(e);
        }

        private void HandleDragOver(DragEventArgs e)
        {
            var dragGroup = (DragGroup) e.Data.GetData(typeof (DragGroup));
            var offsets = dragGroup.Offsets;

            if (offsets == null) return;

            var moves = new List<UndoableAction>();

            Debug.WriteLine("----Dragging----");
            Debug.WriteLine("Mouse: " + e.GetPosition(TreeCanvas));
            foreach (var kv in offsets)
            {
                var node = kv.Key;
                var offset = kv.Value;

                Point position = GetTreeNodePosition(e.GetPosition(TreeCanvas), node);
                position.X += offset.X;
                position.Y += offset.Y;

                var action = new MoveNodeAction(node, position);
                moves.Add(action);
                Debug.WriteLine("Node: " + kv.Key.TopCenter + "\t" + kv.Key.Offset + "\t" + offsets[kv.Key] + "\t" + position);
            }

            var combinedAction = new CombinedAction(moves);
            combinedAction.Execute();

            foreach (var kv in offsets)
            {
                Debug.WriteLine("Node: " + kv.Key.TopCenter + "\t" + kv.Key.Offset + "\t" + offsets[kv.Key]);
            }

        }

        private void TreeCanvas_Drop(object sender, DragEventArgs e)
        {
            OnExecutingAction();
            HandleDrop(e);
        }

        private void HandleDrop(DragEventArgs e)
        {
            var dragGroup = (DragGroup)e.Data.GetData(typeof(DragGroup));
            var offsets = dragGroup.Offsets;

            bool oldAlignValue = dragGroup.InitNode.NeedsCanvasAlignment;
            try
            {
                dragGroup.InitNode.NeedsCanvasAlignment = true;

                var destination = GetAlignedNodePosition(e.GetPosition(TreeCanvas), dragGroup.InitNode);
                var firstMove = new MoveNodeAction(dragGroup.InitNode, destination,
                                                   dragGroup.InitPositions[dragGroup.InitNode]);
                dragGroup.InitNode.IsBeingDragged = false;

                var otherMoves = new List<UndoableAction>();
                foreach (var kv in offsets.Where(x => x.Key != dragGroup.InitNode))
                {
                    var node = kv.Key;
                    var offset = kv.Value;
                    Point position = GetAlignedNodePosition(e.GetPosition(TreeCanvas), dragGroup.InitNode);
                    position.X += offset.X;
                    position.Y += offset.Y;

                    node.Offset = null;
                    node.IsBeingDragged = false;

                    position = GetTreeNodePosition(position, node);

                    otherMoves.Add(new MoveNodeAction(node, position, dragGroup.InitPositions[node]));
                }

                var otherAction = new CombinedAction(otherMoves);
                var combined = new CombinedAction(firstMove, otherAction);
                History.ExecuteAction(combined);
            }
            finally
            {
                dragGroup.InitNode.NeedsCanvasAlignment = oldAlignValue;
            }
        }

        public Point GetAlignedNodePosition(Point mousePosition, INode node)
        {
            var position = GetTreeNodePosition(mousePosition, node);
            if (node.NeedsCanvasAlignment)
            {
                double shiftX = position.X % 10 < 5 ? 0 : 10;
                position.X = position.X - position.X % 10 + shiftX;

                double shiftY = position.Y % 10 < 5 ? 0 : 10;
                position.Y = position.Y - position.Y % 10 + shiftY;
            }
            return position;
        }

        public Point GetTreeNodePosition(Point mousePosition, INode node)
        {
            double offsetX = node.Offset.HasValue ? node.Offset.Value.X : 0;
            double offsetY = node.Offset.HasValue ? node.Offset.Value.Y : 0;

            var position = new Point(mousePosition.X - offsetX, mousePosition.Y - offsetY);

            return position;
        }
        #endregion

        #region Serialization
        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            XDocument xdoc = XDocument.Load(reader);

            var nodes = ReadNodes(xdoc);
            foreach (var node in nodes)
            {
                TreeCanvas.Children.Add(node);
            }

            var frames = ReadFrames(xdoc);
            foreach (var frame in frames)
            {
                TreeCanvas.Children.Add(frame);
            }

            var links = ReadBinders(xdoc, nodes.Cast<INode>().Union(frames).ToList());
            foreach (var binder in links)
            {
                TreeCanvas.Children.Add((UIElement)binder);
                binder.RefreshPoints();
            }
        }

        private List<NodeFrame> ReadFrames(XDocument xdoc)
        {
            var frames = xdoc.Root.Element("frames");
            if (frames == null) return new List<NodeFrame>();

            var res = new List<NodeFrame>();
            foreach (XElement xml in frames.Elements())
            {
                NodeFrame frame = XmlSerializationUtility.Deserialize<NodeFrame>(xml.ToString());
                SetFrameEvents(frame);
                res.Add(frame);
            }
            return res;
        }

        private List<TreeNode> ReadNodes(XDocument xdoc)
        {
            var nodes = xdoc.Root.Element("nodes");
            if (nodes == null) return new List<TreeNode>();

            var res = new List<TreeNode>();
            foreach (XElement xml in nodes.Elements())
            {
                TreeNode node = XmlSerializationUtility.Deserialize<TreeNode>(xml.ToString());
                SetNodeEvents(node);
                res.Add(node);
            }
            return res;
        }

        private List<IBinder> ReadBinders(XDocument xdoc, List<INode> nodes)
        {
            return ReadBinder<ArrowLink>(xdoc.Root.Element("arrows"), nodes)
                .Cast<IBinder>()
                .Union(ReadBinder<LineLink>(xdoc.Root.Element("lines"), nodes))
                .Union(ReadBinder<TriangleLink>(xdoc.Root.Element("triangles"), nodes))
                .ToList();
        }

        private List<TBinder> ReadBinder<TBinder>(XElement element, List<INode> nodes)
            where TBinder : UIElement, IBinder
        {
            var binders = new List<TBinder>();
            foreach (XElement xml in element.Elements())
            {
                TBinder link = XmlSerializationUtility.Deserialize<TBinder>(xml.ToString());

                Guid fromGuid = Guid.Parse(xml.Element("From").Value);
                Guid toGuid = Guid.Parse(xml.Element("To").Value);

                INode from = nodes.First(x => x.Guid == fromGuid);
                INode to = nodes.First(x => x.Guid == toGuid);

                link.From = from;
                link.To = to;

                from.Links.Add(link);
                to.Links.Add(link);

                SetBinderEvents(link);

                binders.Add(link);
            }
            return binders;
        }

        private void SetBinderEvents<TBinder>(TBinder link) where TBinder : IBinder, IMenuChangeableObject
        {
            link.MenuOpened += OnObjectMenuOpened;
            link.MenuClosed += OnObjectMenuClosed;
            link.Selected += SelectElement;
            link.Delete += DeleteBinder;
        }

        public void WriteXml(XmlWriter writer)
        {
            writer.WriteElementString("version", "2013/11/23");
            WriteChildren<TreeNode>(writer, "nodes");
            WriteChildren<NodeFrame>(writer, "frames");
            WriteChildren<LineLink>(writer, "lines");
            WriteChildren<ArrowLink>(writer, "arrows");
            WriteChildren<TriangleLink>(writer, "triangles");
        }

        private void WriteChildren<T>(XmlWriter writer, string nodeName)
        {
            XElement node = new XElement(nodeName);
            SaveNodes(node, TreeCanvas.Children.OfType<T>());
            writer.WriteRaw(node.ToString());
        }

        private void SaveNodes<T>(XElement parent, IEnumerable<T> objects)
        {
            foreach (var obj in objects)
            {
                parent.Add(XElement.Parse(XmlSerializationUtility.Serialize(obj)));
            }
        }
        #endregion

        public void OnExecutingAction()
        {
            if (OpeningFromFile) return;
            IsSaved = false;
        }

        private void FontChanged(object sender, FontChangedEventArgs e)
        {
            var selected = TreeCanvas.Children.OfType<TreeNode>().Where(x => x.IsSelected);
            var actions = new List<UndoableAction>();
            foreach (var node in selected)
            {
                var action = new ChangeFontAction(node, e.Font);
                actions.Add(action);
            }

            var combined = new CombinedAction(actions);
            History.ExecuteAction(combined);
        }

        private void NodeTextChanged(object sender, NodeTextChangedEventArgs e)
        {
            var action = new NodeTextChangedAction(e.Node, e.OldText, e.NewText);
            History.ExecuteAction(action);
        }

        #region Frame creating

        private Point? _frameStart;
        private NodeFrame _frame;


        private void TreeCanvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            Point point = e.GetPosition(this);
            RegisterFrame(point);
        }

        private void RegisterFrame(Point point)
        {
            _frameStart = point;
        }

        private void TreeCanvas_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (_frameStart == null) return;

            if (_frame == null)
            {
                ClearTempFrame();
                return;
            }

            Point point = e.GetPosition(this);
            Point start = _frameStart.Value;

            double width = Math.Abs(point.X - start.X);
            width = Math.Max(width, ApplicationSettings.Default.MinimalFrameSize * 2);

            double height = Math.Abs(point.Y - start.Y);
            height = Math.Max(height, ApplicationSettings.Default.MinimalFrameSize*2);

            _frame.Size = new Size(width, height);
            _frame.IsCreated = true;

            double x = Math.Min(point.X, start.X) + width / 2;
            double y = Math.Min(point.Y, start.Y);

            TreeCanvas.Children.Remove(_frame);

            var action = new CreateNodeAction(TreeCanvas, _frame, new Point(x, y));
            History.ExecuteAction(action);

            _frame.IsSelected = true;
            _frame.Focus();

            ClearTempFrame();
        }

        private void ClearTempFrame()
        {
            _frame = null;
            _frameStart = null;
        }

        private void TreeCanvas_MouseMove(object sender, MouseEventArgs e)
        {
            if (_frameStart != null)
            {
                UnselectAllElements();
                Point point = e.GetPosition(this);
                Point start = _frameStart.Value;
                CheckTempFrame();

                double width = Math.Abs(point.X - start.X);
                double height = Math.Abs(point.Y - start.Y);

                _frame.Size = new Size(width, height);
                double x = Math.Min(point.X, start.X) + width/2;
                double y = Math.Min(point.Y, start.Y);

                _frame.TopCenter = new Point(x, y);
            }
            else if (_selectionStart != null)
            {
                Point end = e.GetPosition(TreeCanvas);
                Point start = _selectionStart.Value;
                const int minPixelOffset = 3;
                if (!_isSelectionInProcess && Math.Abs(start.X - end.X) < minPixelOffset && Math.Abs(start.Y - end.Y) < minPixelOffset)
                    return;
                _isSelectionInProcess = true;
                CheckSelectionRect();

                double x = Math.Min(start.X, end.X);
                double y = Math.Min(start.Y, end.Y);
                double width = Math.Abs(start.X - end.X);
                double height = Math.Abs(start.Y - end.Y);

                Canvas.SetLeft(_selectionRect, x);
                Canvas.SetTop(_selectionRect, y);
                _selectionRect.Width = width;
                _selectionRect.Height = height;
            }
        }

        private void CheckSelectionRect()
        {
            if (_selectionRect != null) return;
            _selectionRect = new Rectangle() { Fill = SystemColors.HighlightBrush, Opacity = 0.2 };
            TreeCanvas.Children.Add(_selectionRect);
        }

        private void CheckTempFrame()
        {
            if (_frame != null) return;
            _frame = CreateNodeFrame();
        }

        private NodeFrame CreateNodeFrame()
        {
            var frame = new NodeFrame();
            SetFrameEvents(frame);
            TreeCanvas.Children.Add(frame);
            return frame;
        }

        private void SetFrameEvents(NodeFrame frame)
        {
            frame.Delete += DeleteNodes;
            frame.Selected += SelectElement;
            frame.DragInit += DragInit;
            frame.MenuOpened += OnObjectMenuOpened;
            frame.MenuClosed += OnObjectMenuClosed;
            frame.MoveAndResizeComplete += (action) => History.ExecuteAction(action);

            SetBindingEvents(frame);
        }

        #endregion

        #region Deletion
        public void DeleteNodes(object sender, EventArgs e)
        {
            var selectedNodes = TreeCanvas.Children.OfType<INode>().Where(x => x.IsSelected).ToList();

            var actions = new List<UndoableAction>();
            foreach (var node in selectedNodes)
            {
                var binderActions = new List<UndoableAction>();
                foreach (var binder in node.Links)
                {
                    binderActions.Add(new DeleteBinderAction(TreeCanvas, binder));
                }
                var bindersDeletion = new CombinedAction(binderActions);

                var nodeDeletion = new DeleteNodeAction(TreeCanvas, node);
                var action = new CombinedAction(bindersDeletion, nodeDeletion);
                actions.Add(action);
            }

            var selectedBinders = TreeCanvas.Children.OfType<IBinder>().Where(x => x.IsSelected)
                .Where(x => !selectedNodes.Contains(x.From) && !selectedNodes.Contains(x.To))
                .ToList();
            foreach (var binder in selectedBinders)
            {
                var action = new DeleteBinderAction(TreeCanvas, binder);
                actions.Add(action);
            }

            var combined = new CombinedAction(actions);
            UnselectAllElements();
            History.ExecuteAction(combined);

            TreeCanvas.Focus();
        }

        public void DeleteBinder(object sender, DeleteBinderEventArgs e)
        {
            var action = new DeleteBinderAction(TreeCanvas, e.Binder);
            History.ExecuteAction(action);
        }
        #endregion

        #region Selection
        private Point? _selectionStart;
        private bool _isSelectionInProcess;
        private Rectangle _selectionRect;

        private HashSet<ISelectable> _selectedElements = new HashSet<ISelectable>(); 

        private void RegisterSelection(Point start)
        {
            _selectionStart = start;
        }

        private void FinishSelection(MouseButtonEventArgs e)
        {
            var end = e.GetPosition(TreeCanvas);

            var selectionRect = new Rect(Math.Min(_selectionStart.Value.X, end.X),
                                         Math.Min(_selectionStart.Value.Y, end.Y),
                                         Math.Abs(_selectionStart.Value.X - end.X),
                                         Math.Abs(_selectionStart.Value.Y - end.Y));
            var intersectingItems = TreeCanvas.Children.OfType<ISelectable>()
                                  .ToList();

            if (!Keyboard.IsKeyDown(Key.LeftCtrl) && !Keyboard.IsKeyDown(Key.RightCtrl))
                _selectedElements.Clear();

            var itemsToSelect = intersectingItems
                .Where(x => x.Intersects(selectionRect))
                .Where(x => !x.IsSelected)
                .ToArray();

            var nodesToUnselect = intersectingItems.Where(x => x.Intersects(selectionRect))
                                       .Where(x => x.IsSelected)
                                       .ToList();

            foreach (var item in itemsToSelect)
            {
                _selectedElements.Add(item);
            }
            foreach (var item in nodesToUnselect)
            {
                _selectedElements.Remove(item);
            }

            UpdateSelectedNodes();
            _isSelectionInProcess = false;
        }

        private void SelectElement(object sender, SelectionEventArgs e)
        {
            var node = e.ClickedElement;
            if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
            {
                if (node.IsSelected)
                    _selectedElements.Remove(node);
                else _selectedElements.Add(node);
            }
            else
            {
                _selectedElements.Clear();
                _selectedElements.Add(node);
            }

            UpdateSelectedNodes();
        }

        private void UpdateSelectedNodes()
        {
            var elements = TreeCanvas.Children.OfType<ISelectable>().ToList();
            foreach (var selectable in elements)
            {
                selectable.IsSelected = _selectedElements.Contains(selectable);
            }

            var mainElement = _selectedElements.OfType<INode>()
                    .OrderBy(x => x.TopCenter.Y)
                    .ThenBy(x => x.TopCenter.X)
                    .FirstOrDefault();

            if (mainElement != null)
            {
                ((UIElement)mainElement).Focus();
            }
            else TreeCanvas.Focus();

            OnSelectionChanged();
        }

        private void UnregisterSelection()
        {
            _selectionStart = null;
            TreeCanvas.Children.Remove(_selectionRect);
            _selectionRect = null;
        }

        public void UnselectAllElements()
        {
            _selectedElements.Clear();
            UpdateSelectedNodes();
        }

        private void SelectAll_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            _selectedElements = new HashSet<ISelectable>(TreeCanvas.Children.OfType<ISelectable>());
            UpdateSelectedNodes();
        }

        #endregion

        #region Binders handling

        private NodeBinding _nodeBinding;

        private void InitBinding(object sender, InitBindingEventArgs e)
        {
            var selectedNodes = TreeCanvas.Children.OfType<INode>().Where(x => x.IsSelected).ToList();

            Func<IBinder> factoryWrapper = () =>
                                     {
                                         var res = e.BinderFactory();
                                         SetBinderEvents(res);
                                         return res;
                                     };

            _nodeBinding = new NodeBinding(selectedNodes, factoryWrapper);
            foreach (var node in selectedNodes)
            {
                node.IsBeingLinked = true;
            }
        }

        private void CompleteBinding(object sender, CompleteBindingEventArgs e)
        {
            if (_nodeBinding == null) return;

            var target = e.Target;

            var actions = new List<UndoableAction>();
            foreach (var binder in _nodeBinding.Binders)
            {
                binder.To = target;
                var action = new NodeBindAction(TreeCanvas, binder);
                actions.Add(action);
            }

            var combined = new CombinedAction(actions);
            History.ExecuteAction(combined);

            ResetBinding();
        }

        public void ResetBinding()
        {
            if (_nodeBinding == null) return;
            _nodeBinding.SourceNodes.ForEach(x => x.IsBeingLinked = false);
            _nodeBinding = null;
        }

        private void SetBindingEvents(IBindable bindable)
        {
            bindable.InitBinding += InitBinding;
            bindable.CompleteBinding += CompleteBinding;
        }

        #endregion

        #region MenuChangeableObjects

        private Dictionary<string, object> _snapshot; 

        public void OnObjectMenuOpened(object sender, MenuChangesEventArgs e)
        {
            _snapshot = e.Obj.MakeSnapshot();
        }

        public void OnObjectMenuClosed(object sender, MenuChangesEventArgs e)
        {
            if (!e.Obj.DetectChanges(_snapshot)) return;
            var action = new SnapshotAction(e.Obj, _snapshot, e.Obj.MakeSnapshot());
            History.ExecuteAction(action);

            _snapshot = null;
        }


        #endregion

        #region Mouse handlers

        private void TreeCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            ResetBinding();
            if (!Keyboard.IsKeyDown(Key.LeftCtrl) && !Keyboard.IsKeyDown(Key.RightCtrl))
                UnselectAllElements();

            TreeCanvas.Focus();

            if (e.ClickCount < 2)
            {
                RegisterSelection(e.GetPosition(this));
            }
            else
            {
                base.OnMouseDoubleClick(e);

                CreateTreeNode(e);
            }

            e.Handled = true;
        }

        private void TreeCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;

            if (!_isSelectionInProcess)
            {
                UnregisterSelection();
                return;
            }

            FinishSelection(e);

            UnregisterSelection();
        }

        #endregion

        private void Undo_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = History.CanUndo;
        }

        private void Redo_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = History.CanRedo;
        }

        private void Undo_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            ResetBinding();
            History.Undo();
            FocusTree();
        }

        private void FocusTree()
        {
            if (!TreeCanvas.Children.OfType<UIElement>().Any(x => x.IsFocused))
                TreeCanvas.Focus();
        }

        private void Redo_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            ResetBinding();
            History.Redo();
            FocusTree();
        }

        private void DeleteCmd_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = _selectedElements.Count > 0;
        }

        private void DeleteCmd_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            DeleteNodes(sender, e);
        }

        public event Action SelectionChanged;

        protected virtual void OnSelectionChanged()
        {
            var handler = SelectionChanged;
            if (handler != null) handler();
        }

        #region Clipboard

        private const string ClipboardFormatName = "Treebuilder v3.0 format";

        private T CopyObject<T>(T obj)
        {
            var copy = XmlSerializationUtility.Copy(obj);
            return copy;
        }

        private void CopyCmd_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            var nodes = CopyNodes<TreeNode>();
            var frames = CopyNodes<NodeFrame>();

            var guids = new HashSet<Guid>(nodes.Cast<INode>().Union(frames).Select(x => x.Guid));

            var copy = new CopyObjectDTO
                           {
                               Nodes = nodes,
                               Frames = frames,
                               Lines = CopyBinders<LineLink>(guids),
                               Arrows = CopyBinders<ArrowLink>(guids),
                               Triangles = CopyBinders<TriangleLink>(guids)
                           };
            var xdoc = SerializeCopyObject(copy);

            Clipboard.SetData(ClipboardFormatName, xdoc.ToString(SaveOptions.DisableFormatting));
        }

        public XDocument SerializeCopyObject(CopyObjectDTO copy)
        {
            var xdoc = new XDocument();
            xdoc.Add(new XElement("root"));
            WriteElements(xdoc.Root, "nodes", copy.Nodes);
            WriteElements(xdoc.Root, "frames", copy.Frames);
            WriteElements(xdoc.Root, "lines", copy.Lines);
            WriteElements(xdoc.Root, "arrows", copy.Arrows);
            WriteElements(xdoc.Root, "triangles", copy.Triangles);

            return xdoc;
        }

        private CopyObjectDTO DeserializeCopyObject(XDocument xdoc)
        {
            var res = new CopyObjectDTO();
            res.Nodes = ReadNodes(xdoc);
            res.Frames = ReadFrames(xdoc);

            var nodes = res.Nodes.Cast<INode>().Union(res.Frames).ToList();

            res.Lines = ReadBinder<LineLink>(xdoc.Root.Element("lines"), nodes);
            res.Arrows = ReadBinder<ArrowLink>(xdoc.Root.Element("arrows"), nodes);
            res.Triangles = ReadBinder<TriangleLink>(xdoc.Root.Element("triangles"), nodes);

            return res;
        }

        private void WriteElements<T>(XElement root, string parentName, IEnumerable<T> objects)
        {
            var parent = new XElement(parentName);
            root.Add(parent);
            SaveNodes(parent, objects);
        }

        private List<T> CopyNodes<T>()
            where T : INode
        {
            var nodes = new List<T>();
            foreach (var node in TreeCanvas.Children.OfType<T>().Where(x => x.IsSelected))
            {
                var copy = CopyObject(node);
                copy.TopCenter = new Point(copy.TopCenter.X + 20, copy.TopCenter.Y + 20);
                nodes.Add(copy);
            }
            return nodes;
        }

        private List<T> CopyBinders<T>(HashSet<Guid> guids)
            where T : IBinder
        {
            var binders = TreeCanvas.Children.OfType<T>()
                             .Where(x => guids.Contains(x.From.Guid) || guids.Contains(x.To.Guid))
                             .Select(x => new {Init = x, Copy = CopyObject(x)})
                             .ToList();
            foreach (var pair in binders)
            {
                pair.Copy.From = pair.Init.From;
                pair.Copy.To = pair.Init.To;
            }

            return binders.Select(x => x.Copy).ToList();
        }

        private void Paste_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = Clipboard.ContainsData(ClipboardFormatName);
        }

        private void Paste_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            var str = Clipboard.GetData(ClipboardFormatName) as string;
            var xdoc = XDocument.Parse(str);
            var data = DeserializeCopyObject(xdoc);

            UnselectAllElements();

            var action = new PasteAction(TreeCanvas, data);
            History.ExecuteAction(action);
        }

        private void CopyCmd_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = TreeCanvas.Children.OfType<INode>().Any(x => x.IsSelected);
        }
        #endregion
    }
}
